/*
 * AIEngine a new generation network intrusion detection system.
 *
 * Copyright (C) 2013-2023  Luis Campo Giralte
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 * Written by Luis Campo Giralte <luis.camp0.2009@gmail.com>
 *
 */
#include <pwd.h>
#include "System.h"
#include "UnitConverter.h"

#if defined(IS_DARWIN)
#include <mach/mach.h>
#endif

namespace aiengine {

boost::posix_time::ptime System::start_time_ = boost::posix_time::microsec_clock::local_time();

System::System():
	is_memory_lock_((mlockall(MCL_CURRENT) == 0) ? true: false)
#if defined(HAVE_VALGRIND) && defined(IS_LINUX)
	,is_valgrind_enable_((RUNNING_ON_VALGRIND == 1) ? true : false)
#endif
	{
        // In macos mlockall returns -1 and errno is set to ENOSYS
        // that means that the syscall is not implemented

	uname(&system_info_),
	getrusage(RUSAGE_SELF, &usage_);
}

System::~System() {

	munlockall();
}

void System::statistics(std::basic_ostream<char> &out) const {

	struct rusage usage;
	uint64_t virtual_memory = 0;
	uint64_t resident_memory = 0;
	std::string unit("Bytes");
	std::string runit("Bytes");
	struct passwd *pass = getpwuid(geteuid());
	std::string_view username = "not available";
	std::ostringstream os_release;

	if (pass)
		username = pass->pw_name;

	virtual_memory = retrieve_virtual_memory();

	unitConverter(virtual_memory, unit);

	getrusage(RUSAGE_SELF, &usage);

	resident_memory = usage.ru_maxrss;
	unitConverter(resident_memory, runit);

	std::string arch(system_info_.machine);

	os_release << system_info_.sysname << " " << system_info_.release;
        out << "System process statistics" << std::dec <<  "\n"
                << "\t" << "Architecture:               " << std::setw(11 - arch.length()) << arch << "\n"
                << "\t" << "Version:  " << std::setw(24) << VERSION << "\n"
                << "\t" << "Hostname: " << std::setw(24) <<  system_info_.nodename << "\n"
                << "\t" << "System: " << std::setw(26) << os_release.str() << "\n"
                << "\t" << "Pid:    " << std::setw(26) << (pid_t)getpid() << "\n"
                << "\t" << "User:     " << std::setw(24) << username.data() << "\n"
		<< "\t" << "Elapsed time:          " << ElapsedTime().compute(start_time_) << "\n"
		<< "\t" << "Virtual memory size:    " << std::setw(9 - unit.length()) << virtual_memory << " " << unit << "\n"
#if defined(IS_LINUX)
#if defined(HAVE_VALGRIND)
		<< "\t" << "Valgrind enable:             " << std::setw(5) << (is_valgrind_enable_ ? "yes":"no") << "\n"
#endif
		<< "\t" << "Lock memory pages:           " << std::setw(5) << (is_memory_lock_ ? "yes":"no") << "\n"
		<< "\t" << "Resident memory size:   " << std::setw(9 - runit.length()) << resident_memory << " " << runit << "\n"
		<< "\t" << "Shared memory size:          " << std::setw(5) << usage.ru_ixrss << "\n"
		<< "\t" << "Unshared data size:          " << std::setw(5) << usage.ru_idrss << "\n"
		<< "\t" << "Unshared stack size:         " << std::setw(5) << usage.ru_isrss << "\n"
#endif
		<< "\t" << "Page reclaims:             " << std::setw(7) << usage.ru_minflt << "\n"
		<< "\t" << "Page faults:                 " << std::setw(5) << usage.ru_majflt << "\n"
		<< "\t" << "Swaps:                       " << std::setw(5) << usage.ru_nswap << "\n"
		<< "\t" << "Block input operations: " << std::setw(10) << usage.ru_inblock << "\n"
		<< "\t" << "Block output operations:     " << std::setw(5) << usage.ru_oublock << "\n"
		<< "\t" << "IPC messages sent:           " << std::setw(5) << usage.ru_msgsnd << "\n"
		<< "\t" << "IPC messages received:       " << std::setw(5) << usage.ru_msgrcv << "\n"
		<< "\t" << "Signal received:             " << std::setw(5) << usage.ru_nsignals << "\n"
		<< "\t" << "Voluntary ctx switches: " << std::setw(10) << usage.ru_nvcsw << "\n"
		<< "\t" << "Involuntary ctx switches: " << std::setw(8) << usage.ru_nivcsw << std::endl;
}

std::string System::getOSName() const {
	std::ostringstream os;

        os << system_info_.sysname;
	return os.str();
}

std::string System::getNodeName() const {
	std::ostringstream os;

        os << system_info_.nodename;
	return os.str();
}

std::string System::getReleaseName() const {
	std::ostringstream os;

        os << system_info_.release;
	return os.str();
}

std::string System::getVersionName() const {
	std::ostringstream os;

        os << system_info_.version;
	return os.str();
}

std::string System::getMachineName() const {
	std::ostringstream os;

        os << system_info_.machine;
	return os.str();
}

std::string System::getHostName() const {
	char name[100];
	boost::system::error_code ec;

	if (boost::asio::detail::socket_ops::gethostname(name, sizeof(name), ec) != 0)
		return std::string();
	return std::string(name);
}

void System::statistics(Json &out) const {

	struct rusage usage;
	std::ostringstream proc_file, elapsed_str;

	out["sysname"] = system_info_.sysname;
	out["nodename"] = system_info_.nodename;
	out["release"] = system_info_.release;
	out["os_version"] = system_info_.version;
	out["machine"] = system_info_.machine;
	out["version"] = VERSION;

	out["euid"] = geteuid();
	out["pid"] = (pid_t)getpid();

	getrusage(RUSAGE_SELF, &usage);

	out["elapsed_time"] = ElapsedTime().compute(start_time_);
	out["virtual_memory"] = retrieve_virtual_memory();
#if defined(IS_LINUX)
#if defined(HAVE_VALGRIND)
	out["valgrind"] = is_valgrind_enable_;
#endif
	out["lock_memory_pages"] = is_memory_lock_;
	out["resident_memory"] = usage.ru_maxrss;
	out["shared_memory"] = usage.ru_ixrss;
	out["unshared_data"] = usage.ru_idrss;
	out["unshared_stack"] = usage.ru_isrss;
#endif
}

int64_t System::retrieve_virtual_memory() const {

	int64_t value = 0;
#if defined(IS_LINUX)
	std::ostringstream proc_file;
	proc_file << "/proc/" << getpid() << "/stat";

	std::vector<std::string> items;
	try {
		std::string item;
		std::ifstream fd (proc_file.str());
		while(std::getline(fd, item, ' '))
			items.push_back(item);

		// The virtual memory is on the 22 index value
		value = std::stol(items.at(22));
	} catch ( ... ) { /* LCOV_EXCL_LINE */

	}
#elif defined(IS_DARWIN)
	mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
	mach_task_basic_info_data_t taskinfo;

	if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&taskinfo, &count) == KERN_SUCCESS)
		value = taskinfo.resident_size;
#endif
	return value;
}

} // namespace aiengine
